/*
 * Copyright (c) 2002, 2003, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package com.sun.corba.se.impl.orbutil.fsm;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.Iterator;

import org.omg.CORBA.INTERNAL;

import com.sun.corba.se.impl.orbutil.ORBUtility;

import com.sun.corba.se.spi.orbutil.fsm.Input;
import com.sun.corba.se.spi.orbutil.fsm.Guard;
import com.sun.corba.se.spi.orbutil.fsm.Action;
import com.sun.corba.se.spi.orbutil.fsm.ActionBase;
import com.sun.corba.se.spi.orbutil.fsm.State;
import com.sun.corba.se.spi.orbutil.fsm.StateEngine;
import com.sun.corba.se.spi.orbutil.fsm.StateImpl;
import com.sun.corba.se.spi.orbutil.fsm.FSM;
import com.sun.corba.se.spi.orbutil.fsm.FSMImpl;

import com.sun.corba.se.impl.orbutil.fsm.GuardedAction;

/**
 * Encodes the state transition function for a finite state machine.
 *
 * @author Ken Cavanaugh
 */
public class StateEngineImpl implements StateEngine {

  // An action that does nothing at all.
  private static Action emptyAction = new ActionBase("Empty") {
    public void doIt(FSM fsm, Input in) {
    }
  };

  private boolean initializing;
  private Action defaultAction;

  public StateEngineImpl() {
    initializing = true;
    defaultAction = new ActionBase("Invalid Transition") {
      public void doIt(FSM fsm, Input in) {
        throw new INTERNAL(
            "Invalid transition attempted from " +
                fsm.getState() + " under " + in);
      }
    };
  }

  public StateEngine add(State oldState, Input input, Guard guard, Action action,
      State newState) throws IllegalArgumentException,
      IllegalStateException {
    mustBeInitializing();

    StateImpl oldStateImpl = (StateImpl) oldState;
    GuardedAction ga = new GuardedAction(guard, action, newState);
    oldStateImpl.addGuardedAction(input, ga);

    return this;
  }

  public StateEngine add(State oldState, Input input, Action action,
      State newState) throws IllegalArgumentException,
      IllegalStateException {
    mustBeInitializing();

    StateImpl oldStateImpl = (StateImpl) oldState;
    GuardedAction ta = new GuardedAction(action, newState);
    oldStateImpl.addGuardedAction(input, ta);

    return this;
  }

  public StateEngine setDefault(State oldState, Action action, State newState)
      throws IllegalArgumentException, IllegalStateException {
    mustBeInitializing();

    StateImpl oldStateImpl = (StateImpl) oldState;
    oldStateImpl.setDefaultAction(action);
    oldStateImpl.setDefaultNextState(newState);

    return this;
  }

  public StateEngine setDefault(State oldState, State newState)
      throws IllegalArgumentException, IllegalStateException {
    return setDefault(oldState, emptyAction, newState);
  }

  public StateEngine setDefault(State oldState)
      throws IllegalArgumentException, IllegalStateException {
    return setDefault(oldState, oldState);
  }

  public void done() throws IllegalStateException {
    mustBeInitializing();

    // optimize FSM here if desired.  For example,
    // we could choose different strategies for implementing
    // the state transition function based on the distribution
    // of values for states and input labels.

    initializing = false;
  }

  public void setDefaultAction(Action act) throws IllegalStateException {
    mustBeInitializing();
    defaultAction = act;
  }

  public void doIt(FSM fsm, Input in, boolean debug) {
    // This method is present only for debugging.
    // innerDoIt does the actual transition.

    if (debug) {
      ORBUtility.dprint(this, "doIt enter: currentState = " +
          fsm.getState() + " in = " + in);
    }

    try {
      innerDoIt(fsm, in, debug);
    } finally {
      if (debug) {
        ORBUtility.dprint(this, "doIt exit");
      }
    }
  }

  private StateImpl getDefaultNextState(StateImpl currentState) {
    // Use the currentState defaults if
    // set, otherwise use the state engine default.
    StateImpl nextState = (StateImpl) currentState.getDefaultNextState();
    if (nextState == null)
    // The state engine default never changes the state
    {
      nextState = currentState;
    }

    return nextState;
  }

  private Action getDefaultAction(StateImpl currentState) {
    Action action = currentState.getDefaultAction();
    if (action == null) {
      action = defaultAction;
    }

    return action;
  }

  private void innerDoIt(FSM fsm, Input in, boolean debug) {
    if (debug) {
      ORBUtility.dprint(this, "Calling innerDoIt with input " + in);
    }

    // Locals needed for performing the state transition, once we determine
    // the required transition.
    StateImpl currentState = null;
    StateImpl nextState = null;
    Action action = null;

    // Do until no guard has deferred.
    boolean deferral = false;
    do {
      deferral = false; // clear this after each deferral!
      currentState = (StateImpl) fsm.getState();
      nextState = getDefaultNextState(currentState);
      action = getDefaultAction(currentState);

      if (debug) {
        ORBUtility.dprint(this, "currentState      = " + currentState);
        ORBUtility.dprint(this, "in                = " + in);
        ORBUtility.dprint(this, "default nextState = " + nextState);
        ORBUtility.dprint(this, "default action    = " + action);
      }

      Set gas = currentState.getGuardedActions(in);
      if (gas != null) {
        Iterator iter = gas.iterator();

        // Search for a guard that is not DISABLED.
        // All DISABLED means use defaults.
        while (iter.hasNext()) {
          GuardedAction ga = (GuardedAction) iter.next();
          Guard.Result gr = ga.getGuard().evaluate(fsm, in);
          if (debug) {
            ORBUtility.dprint(this,
                "doIt: evaluated " + ga + " with result " + gr);
          }

          if (gr == Guard.Result.ENABLED) {
            // ga has the next state and action.
            nextState = (StateImpl) ga.getNextState();
            action = ga.getAction();
            if (debug) {
              ORBUtility.dprint(this, "nextState = " + nextState);
              ORBUtility.dprint(this, "action    = " + action);
            }
            break;
          } else if (gr == Guard.Result.DEFERED) {
            deferral = true;
            break;
          }
        }
      }
    } while (deferral);

    performStateTransition(fsm, in, nextState, action, debug);
  }

  private void performStateTransition(FSM fsm, Input in,
      StateImpl nextState, Action action, boolean debug) {
    StateImpl currentState = (StateImpl) fsm.getState();

    // Perform the state transition.  Pre and post actions are only
    // performed if the state changes (see UML hidden transitions).

    boolean different = !currentState.equals(nextState);

    if (different) {
      if (debug) {
        ORBUtility.dprint(this,
            "doIt: executing postAction for state " + currentState);
      }
      try {
        currentState.postAction(fsm);
      } catch (Throwable thr) {
        if (debug) {
          ORBUtility.dprint(this,
              "doIt: postAction threw " + thr);
        }

        if (thr instanceof ThreadDeath) {
          throw (ThreadDeath) thr;
        }
      }
    }

    try {
      // Note that action may be null in a transition, which simply
      // means that no action is needed.  Note that action.doIt may
      // throw an exception, in which case the exception is
      // propagated after making sure that the transition is properly
      // completed.
      if (action != null) {
        action.doIt(fsm, in);
      }
    } finally {
      if (different) {
        if (debug) {
          ORBUtility.dprint(this,
              "doIt: executing preAction for state " + nextState);
        }

        try {
          nextState.preAction(fsm);
        } catch (Throwable thr) {
          if (debug) {
            ORBUtility.dprint(this,
                "doIt: preAction threw " + thr);
          }

          if (thr instanceof ThreadDeath) {
            throw (ThreadDeath) thr;
          }
        }

        ((FSMImpl) fsm).internalSetState(nextState);
      }

      if (debug) {
        ORBUtility.dprint(this, "doIt: state is now " + nextState);
      }
    }
  }

  public FSM makeFSM(State startState) throws IllegalStateException {
    mustNotBeInitializing();

    return new FSMImpl(this, startState);
  }

  private void mustBeInitializing() throws IllegalStateException {
    if (!initializing) {
      throw new IllegalStateException(
          "Invalid method call after initialization completed");
    }
  }

  private void mustNotBeInitializing() throws IllegalStateException {
    if (initializing) {
      throw new IllegalStateException(
          "Invalid method call before initialization completed");
    }
  }
}

// end of StateEngineImpl.java
